# -*- coding: utf-8 -*-
import datetime
import json
import os
import shutil
import subprocess
import sys

import allure
import pytest

from common.utils.common_functions import exit_program
from common.utils.web_api import web_api


class AllureReport:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            # 创建新的实例
            instance = super().__new__(cls)
            cls._instance = instance
        return cls._instance

    def __init__(self):
        # 判断实例是否已经初始化
        if not hasattr(self, 'authorization_time'):
            # print("AllureReport::__init__")
            self.allure_executable = "allure"
            self.get_allure_executable()
            # 存放报告路径
            self.report_path = "reports"
            # 待替换标签页图标存放位置
            self.web_tab_icon_file = "res/favicon.ico"
            # 待拷贝环境配置参数文件位置
            self.environment_path = "res/environment.properties"
            self.allure_open_bat_file_name = "打开测试报告.bat"
            self.test_case_path = "testdata/data_files/test_case.xlsx"

            # 获得当前时间
            self.report_html_path = self.report_path + "/html_report_" + datetime.datetime.now()\
                .strftime("%Y_%m_%d_%H_%M_%S")
            self.report_history_path = self.report_path + "/report_history"
            self.report_title = "Allure Report"
            self.report_language = "zh-cn"
            self._add_environment_information()

    def get_allure_executable(self):
        if not shutil.which("allure"):
            if os.path.exists("lib\\allure-2.32.0\\bin\\allure"):
                print("未找到 allure 环境，使用本地：lib\\allure-2.32.0\\bin\\allure")
                self.allure_executable = "lib\\allure-2.32.0\\bin\\allure"
            else:
                exit_program("未找到allure环境")

    # 生成报告
    def generate_report(self):
        # allure generate 命令，固定的
        # ./report_time 是生成的 Allure 测试报告的输出路径。
        # -o 输出output
        # ./report 生成的allure报告的路径
        # --clean 清空
        # ./result 测试结果数据（通常是 JSON 文件）所在的路径
        # os.system(f"{self.allure_executable} generate {self.report_history_path} -o {self.report_html_path} "
        #           f"--clean --language {self.report_language}")
        # os.system(f"{self.allure_executable} generate {self.report_history_path} -o {self.report_html_path} --clean")
        # # 执行Allure报告生成命令
        # command = f"{self.allure_executable} generate {self.report_history_path} -o {self.report_html_path} --clean"
        # subprocess.run(command, shell=True)
        # 使用完整路径调用 allure 命令

        result = subprocess.run([self.allure_executable, "generate", self.report_history_path,
                                 "-o", self.report_html_path], capture_output=True, shell=True,
                                text=True)
        if result.returncode != 0:
            print(f"Allure 报告生成失败：{result.stderr}")
        else:
            if os.path.exists(f"{self.report_html_path}/index.html"):
                print("Allure 报告生成成功")
            else:
                print("Allure 报告文件未找到")
        self._set_windows_title()
        self._set_report_title()
        self._history()
        self._set_web_tab_icon()
        self.open_report()

    def _set_web_tab_icon(self):
        if os.path.exists(self.web_tab_icon_file):
            shutil.copy(self.web_tab_icon_file, self.report_html_path)

    def open_report(self):
        shutil.copy("scripts/{}".format(self.allure_open_bat_file_name), self.report_html_path)
        # 自动打开测试报告会显示异常
        try:
            # 切换到 bat 文件所在目录
            os.chdir(self.report_html_path)
            # 使用 subprocess.run 运行 bat 脚本
            result = subprocess.run([self.allure_open_bat_file_name], shell=True, capture_output=True, text=True)
            if result.returncode == 0:
                print("Batch script executed successfully.")
                print("Output:", result.stdout)
            else:
                print("Batch script failed.")
                print("Error:", result.stderr)
        except Exception as e:
            print(f"An error occurred: {e}")

    # 设置报告窗口的标题
    def _set_windows_title(self):
        """  设置打开的 Allure 报告的浏览器窗口标题文案
        @param new_title:  需要更改的标题文案 【 原文案为：Allure Report 】
        @return: 没有返回内容，调用此方法传入需要更改的文案即可修改窗体标题文案
        """
        # report_title_filepath：这里是测试报告HTML标题的相对路径
        report_title_filepath = "{}/index.html".format(self.report_html_path)
        # 定义为只读模型，并定义名称为: f
        with open(report_title_filepath, 'r+', encoding="utf-8") as f:
            # 读取当前文件的所有内容
            all_the_lines = f.readlines()
            f.seek(0)
            f.truncate()
            # 循环遍历每一行的内容，将 "Allure Report" 全部替换为 → new_title(新文案)
            for line in all_the_lines:
                f.write(line.replace("Allure Report", self.report_title))
            # 关闭文件
            f.close()

    def _set_report_title(self):
        # 自定义测试报告标题
        # summary.json：这里是测试报告标题的相对路径，测试报告文案获取的文件地址
        title_filepath = "{}/widgets/summary.json".format(self.report_html_path)
        self._write_json_data(title_filepath, self.report_title)

    # 获取 summary.json 文件的数据内容
    def _get_json_data(self, title_filepath, name):
        # 定义为只读模型，并定义名称为f
        with open(title_filepath, 'rb') as file:
            # 加载json文件中的内容给params
            params = json.load(file)
            # 修改内容
            params['reportName'] = name
        return params

    # 报告标题写入json文件
    def _write_json_data(self, title_filepath, name):
        data = self._get_json_data(title_filepath, name)
        # 定义为写模式，名称定义为r
        with open(title_filepath, 'w', encoding="utf-8") as file:
            # 将dict写入名称为r的文件中
            json.dump(data, file, ensure_ascii=False, indent=4)

    # 添加环境信息
    """
        environment.properties文件格式：
        Browser = Chrome
        Browser.Version = 63.0
        Stand = Production
        乱码：  .encode("unicode_escape").decode()
    """
    def _add_environment_information(self):
        if not os.path.exists("{}".format(self.report_history_path)):
            os.makedirs(f"{self.report_history_path}", exist_ok=True)
        if os.path.exists(self.environment_path):
            # 复制 environment.properties 文件到 self.report_history_path  目录下
            shutil.copy(self.environment_path, self.report_history_path)

    def add_environment_information(self, driver):
        try:
            if not os.path.exists("{}".format(self.report_history_path)):
                os.mkdir("{}".format(self.report_history_path))
            # 获取浏览器名称
            driver_name = driver.get_driver_name()
            # 获取浏览器版本
            driver_version = driver.get_driver_version()
            # 获取操作系统信息
            os_name = os.name
            # 获取 Python 版本
            python_version = sys.version
            # 获取当前工作目录
            cwd = os.getcwd()
            # 假设环境为 Production，可根据实际情况修改
            environment = "Production"
            # 使用 os.path.join 构建文件路径
            env_file_path = os.path.join(self.report_history_path, "environment.properties")
            with open(env_file_path, "w", encoding="utf-8") as env_file:
                # 写入浏览器信息
                env_file.write(f"Browser={driver_name}\n")
                # 写入浏览器版本信息
                env_file.write(f"Browser.Version={driver_version}\n")
                # 写入操作系统信息
                env_file.write(f"OS={os_name}\n")
                # 写入 Python 版本信息
                env_file.write(f"Python.Version={python_version}\n")
                # 写入当前工作目录信息
                env_file.write(f"Working.Directory={cwd}\n")
                # 写入环境信息
                env_file.write(f"Stand={environment}\n")
        except AttributeError:
            print("The driver object does not have the required methods.")
        except Exception as e:
            print(f"An error occurred while adding environment information: {e}")

    # 文件夹复制到另一个文件夹
    def _copy_file(self, path):
        for subdirectory in os.listdir(path):
            # 拼接路径
            full_dir = os.path.join(path, subdirectory)
            # 复制文件
            if os.path.isfile(full_dir):
                shutil.copy(full_dir, "{}/history".format(self.report_history_path))
            # 递归目录
            if os.path.isdir(full_dir):
               self._copy_file(full_dir)

    # 趋势
    # 将report下的history放到report_history下
    def _history(self):
        dir_path = f"{self.report_history_path}/history"
        if not os.path.exists(dir_path):
            os.makedirs(dir_path, exist_ok=True)
        files_path = os.listdir(self.report_path)
        history_path = []
        for file_path in files_path:
            if file_path[:12] == "html_report_":
                history_path.append(file_path)
        if history_path:
            self._copy_file("{}/{}/history".format(self.report_path, history_path[-1]))

    def get_attachment_type(self, file_extension):
        # 定义一个映射字典，将文件扩展名映射到 allure 的附件类型
        attachment_type_mapping = {
            'TXT': allure.attachment_type.TEXT,
            'TEXT': allure.attachment_type.TEXT,
            'CSV': allure.attachment_type.CSV,
            'TSV': allure.attachment_type.TSV,
            # 'URI_LIST': allure.attachment_type.URI_LIST,
            'HTML': allure.attachment_type.HTML,
            'XML': allure.attachment_type.XML,
            'JSON': allure.attachment_type.JSON,
            'YAML': allure.attachment_type.YAML,
            'PCAP': allure.attachment_type.PCAP,
            'PNG': allure.attachment_type.PNG,
            'JPG': allure.attachment_type.JPG,
            'SVG': allure.attachment_type.SVG,
            'GIF': allure.attachment_type.GIF,
            'BMP': allure.attachment_type.BMP,
            'TIFF': allure.attachment_type.TIFF,
            'MP4': allure.attachment_type.MP4,
            'OGG': allure.attachment_type.OGG,
            'WEBM': allure.attachment_type.WEBM,
            'PDF': allure.attachment_type.PDF,
        }
        return attachment_type_mapping.get(file_extension.upper(), None)

    def set_allure_epic_story_feature(self, use_case, excel_read):
        if excel_read.col_epic_pos != -1:
            allure.dynamic.epic(use_case[excel_read.col_epic_pos])

        if excel_read.col_story_pos != -1:
            allure.dynamic.story(use_case[excel_read.col_story_pos])

        if excel_read.col_feature_pos != -1:
            allure.dynamic.feature(use_case[excel_read.col_feature_pos])

    def set_allure_title_desc_severity(self, use_case, excel_read):
        if excel_read.col_title_pos != -1:
            allure.dynamic.title(use_case[excel_read.col_title_pos])

        if excel_read.col_description_pos != -1:
            if use_case[excel_read.col_description_pos] != "":
                allure.dynamic.description(use_case[excel_read.col_description_pos])

        if excel_read.col_severity_pos != -1:
            if use_case[excel_read.col_severity_pos] == "":
                allure.dynamic.severity("normal")
            else:
                allure.dynamic.severity(use_case[excel_read.col_severity_pos])

    def set_allure_testcase_issue_link(self, use_case, excel_read):
        if excel_read.col_testcase_pos != -1:
            if use_case[excel_read.col_testcase_pos] != "":
                use_case_address = use_case[excel_read.col_testcase_pos].split("：")
                if len(use_case_address) == 1:
                    allure.dynamic.testcase(use_case_address[0])
                elif len(use_case_address) == 2:
                    allure.dynamic.testcase(use_case_address[1], use_case_address[0])

        if excel_read.col_issue_pos != -1:
            if use_case[excel_read.col_issue_pos] != "":
                use_case_issue = use_case[excel_read.col_issue_pos].split("：")
                if len(use_case_issue) == 1:
                    allure.dynamic.issue(use_case_issue[0])
                elif len(use_case_issue) == 2:
                    allure.dynamic.issue(use_case_issue[1], use_case_issue[0])

        if excel_read.col_link_pos != -1:
            if use_case[excel_read.col_link_pos] != "":
                use_case_link = use_case[excel_read.col_link_pos].split("：")
                if len(use_case_link) == 1:
                    allure.dynamic.link(use_case_link[0])
                elif len(use_case_link) == 2:
                    allure.dynamic.link(use_case_link[1], name=use_case_link[0])

    def set_allure_attachment(self, use_case, excel_read):
        if excel_read.col_attachment_pos != -1:
            if use_case[excel_read.col_attachment_pos] != "":
                use_case_attachment = use_case[excel_read.col_attachment_pos].split("：")
                if len(use_case_attachment) == 2:
                    attachment_type = self.get_attachment_type(use_case_attachment[0].upper())
                    if use_case_attachment[0].lower() == "text" \
                            and use_case_attachment[1].endswith("txt"):
                        if attachment_type:
                            file_path = use_case_attachment[1]
                            allure.attach.file(file_path, file_path, attachment_type=attachment_type)
                        else:
                            print(f"Unsupported file type: {use_case_attachment[0]}")
                    elif use_case_attachment[1].endswith(use_case_attachment[0].lower()):
                        if attachment_type:
                            file_path = use_case_attachment[1]
                            allure.attach.file(file_path, file_path, attachment_type=attachment_type)
                        else:
                            print(f"Unsupported file type: {use_case_attachment[0]}")
                    else:
                        data = use_case_attachment[1].split("，", 1)
                        if len(data) == 2 and attachment_type:
                            allure.attach(data[1].strip(), data[0], attachment_type)
                        else:
                            print(f"Unsupported file type: {use_case_attachment[0]}")

    def execute_test_case_steps(self, use_case, excel_read):
        try:
            # 执行测试步骤
            test_steps = use_case[excel_read.col_step_pos]
            for test_step in test_steps:
                with allure.step(test_step[0]):
                    # print(f"操作步骤：{test_step[0]}  关键字：{test_step[1]}  测试数据：{test_step[2:]}")
                    getattr(web_api, f"web_{test_step[1]}")(test_step[2:])
        except Exception as e:
            print(f"用例执行失败，异常信息：{str(e)}")
            pytest.fail(f"用例执行失败，异常信息：{str(e)}")

    def execute_preset_condition_steps(self, use_case, excel_read):
        preset_condition_titles = use_case[excel_read.col_preset_condition_pos]
        for preset_condition_title in preset_condition_titles:
            @allure.step(preset_condition_title)
            def execute_steps():
                test_steps = excel_read.preset_steps[preset_condition_title]
                for test_step in test_steps:
                    with allure.step(test_step[0]):
                        # print(f"操作步骤：{test_step[0]}  关键字：{test_step[1]}  测试数据：{test_step[2:]}")
                        getattr(web_api, f"web_{test_step[1]}")(test_step[2:])
            execute_steps()


# 模块级别创建单例对象
allure_report = AllureReport()
